//	--------------------------------------------------------------------
//	Copyright(C) 2006,2007 Zhao Yukun. All rights reserved.
//	This file is a part of the Xeres system.
//	Xeres 2007
//	Contact : xeres.engine@gmail.com
//	$(reservedInfo)
//	$(reservedDeclaration)
//	--------------------------------------------------------------------
///	@file	<XForm.cpp>
///	@path	~/src/gui/xgui/
///	@date	2008/05/27
///	@desc	.

#define _FULLAPI
#include "config/config.h"

#include <algorithm>

#include "lib/utilities/observer.h"

#include "gui/xgui/XForm.h"
#include "gui/composite/composition.h"

namespace xeres {

	IMPL_IOBJECT_CLASS_DYNAMIC( XForm );

	// XForm
	XForm::XForm( void )
		: IGuiForm( Rect<int>( 0 , 0 , 0 , 0 ) )
		, m_offset( 0 , 0 )
		, m_inputMode( false )
	{
		m_refreshHover = false;
		m_refreshFocus = false;
		m_lastMove = Time::Now();
	}
	// XForm
	XForm::XForm( const Rect<int>& placement )
		: IGuiForm( placement )
		, m_offset( 0 , 0 )
		, m_inputMode( false )
	{
		m_refreshHover = false;
		m_refreshFocus = false;
		m_lastMove = Time::Now();
	}
	// XForm
	XForm::XForm( const Pair<int>& size )
		: IGuiForm( Rect<int>( 0 , 0 , size ) )
		, m_offset( 0 , 0 )
		, m_inputMode( false )
	{
		m_refreshHover = false;
		m_refreshFocus = false;
		m_lastMove = Time::Now();
	}
	// ~XForm
	XForm::~XForm( void )
	{
		m_children.clear();
	}
	// SetSize
	bool XForm::SetSize( const Pair<int>& size )
	{
		return __super::SetSize( size );
	}
	// SetAdptiveSize
	bool XForm::SetAdptiveSize( const Pair<int>& size )
	{
		// Recalculate all children size.
		Rect<int> client( 0 , 0 , 1 , 1 );
		for( size_t i = 0 , total = m_children.size() ; i < total ; ++i )
		{
			RefWeak<IGuiComp> comp = m_children[i];
			if( comp->GetVisible() )
			{
				client = client | comp->GetPlacement();
			}
		}

		int x = max( client.right() , size.x );
		int y = max( client.bottom() , size.y );

		return __super::SetSize( Pair<int>( x , y ) );
	}
	// SetParent
	bool XForm::SetParent( RefWeak<IGuiForm> parent )
	{
		if( __super::SetParent( parent ) )
		{
			if( IsTickable() )
			{
				m_parent->AddTickable( this );
			}
			return true;
		}
		return false;
	}
	// ClearFocus
	void XForm::ClearFocus( Updater& updater )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		// Clear focus & send focus out message.
		if( m_focusing.IsValid() )
			m_focusing->OnFocusOut( updater , NULL );
		m_focusing = NULL;
		// Disable input mode.
		m_inputMode = false;
	}
	// ClearFocus
	void XForm::ClearFocus( void )
	{
		Updater updater( this );
		ClearFocus( updater );
	}
	// RequestFocus
	void XForm::RequestFocus( RefWeak<IGuiComp> comp , Updater& updater )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		// Make sure component is a child.
		XS_ASSERT( comp->GetParent() == this );

		// At the same time we make this form focus for parent form.
		if( GetParent().IsValid() )
			GetParent()->RequestFocus( this , updater );

		// The same as focusing comp?
		if( comp == m_focusing )
		{
			if( comp->GetFocus() == false )
				comp->OnFocusIn( updater , NULL );

			ReorderChildren();
			return;
		}

		// Clear focus.
		if( m_focusing.IsValid() )
		{
			m_focusing->OnFocusOut( updater , comp );
			// Reset input mode.
			if( m_inputMode )
				m_inputMode = false;
		}

		// Send focus in.
		RefWeak<IGuiComp> old = m_focusing;
		m_focusing = comp;
		m_focusing->OnFocusIn( updater , old );

		ReorderChildren();
	}
	// RequestFocus
	void XForm::RequestFocus( RefWeak<IGuiComp> comp )
	{
		Updater updater( this );
		RequestFocus( comp , updater );
		UpdateControl();
	}
	// RequestShow
	void XForm::RequestShow( RefWeak<IGuiComp> comp , bool show )
	{
		TRACE_ERROR( _S("XForm::RequestShow: XForm cannot control show/hide of sub component.") );
	}

	// EnterInputMode
	void XForm::EnterInputMode( RefWeak<IGuiComp> comp , Updater& updater )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		// Request focus for input.
		RequestFocus( comp , updater );

		// Turn input mode on.
		m_inputMode = true;

		// Make parent under input mode.
		if( GetParent().IsValid() )
			GetParent()->EnterInputMode( this , updater );
	}
	// GetInputMode
	bool XForm::GetInputMode( void )
	{
		return m_inputMode;
	}
	// OnKeyInput
	HandleInput XForm::OnKeyInput( Updater& updater )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		const Input& input = updater.GetInput();
		HandleInput result = INPUT_UNHANDLED;

		// If a control is in focus, it belongs to this form, and it's enabled, then give
		// it the first chance at handling the message.
		if( m_focusing.IsValid() )
		{
			if( !m_focusing->GetEnable() || !m_focusing->GetVisible() )
			{
				ClearFocus( updater );
			}
			else if( m_focusing->OnKeyInput( updater ) == INPUT_HANDLED )
			{
				return INPUT_HANDLED;
			}
		}

		// Not yet handled, check for focus messages
		if( input.GetKeyInput().IsPressed() )
		{
			switch( input.GetKeyInput().GetKey() )
			{
			case KeyInput::KEY_RIGHTARROW:
			case KeyInput::KEY_DOWNARROW:
				if( m_focusing.IsValid() )
				{
					CycleFocus( true , updater );
					result = INPUT_HANDLED;
				}
				break;

			case KeyInput::KEY_LEFTARROW:
			case KeyInput::KEY_UPARROW:
				if( m_focusing.IsValid() )
				{
					CycleFocus( false , updater );
					result = INPUT_HANDLED;
				}
				break;

			case KeyInput::KEY_TAB: 
				{
					//bool bShiftDown = ( (::GetKeyState( VK_SHIFT ) & 0x8000) != 0 );
					bool bShiftDown = input.IsShiftDown();
					CycleFocus( !bShiftDown , updater );
					result =  INPUT_HANDLED;
				}
			}
		}
		return result;
	}
	// OnMouseInput
	HandleInput XForm::OnMouseInput( Updater& updater )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		const Input& input = updater.GetInput();
		HandleInput result = INPUT_UNHANDLED;

		// Update mouse hovering time.
		if( input.GetAction() == Input::ACTION_MOUSE_MOVE )
		{
			if( !input.IsAnyBtnDown() )
				m_lastMove = Time::Now();
		}

		// Capturing control is first.
		if( m_capturing.IsValid() )
		{
			return m_capturing->OnMouseInput( updater );
		}

		// If a control is in focus, it belongs to this dialog, and it's enabled, then give
		// it the first chance at handling the message.
		if( m_focusing.IsValid() )
		{
			if( !m_focusing->GetEnable() || !m_focusing->GetVisible() )
			{
				ClearFocus( updater );
			}
			else if( m_focusing->OnMouseInput( updater ) == INPUT_HANDLED )
			{
				return INPUT_HANDLED;
			}
		}
		
 		Pair<int> pt = updater.GetPoint();
 		Rect<int> bound = GetBound();
 		if( !bound.is_inside( pt + m_offset ) )
 			return INPUT_UNHANDLED;

		// Not yet handled, see if the mouse is over any controls.
		RefWeak<IGuiComp> comp = GetAtPoint( pt , true , true );
		if( comp.IsValid() && comp->GetEnable() )
		{
			// Change focus if needed.
			switch( input.GetAction() )
			{
			case Input::ACTION_MOUSE_LB_CLICK:
			case Input::ACTION_MOUSE_RB_CLICK:
			case Input::ACTION_MOUSE_MB_CLICK:
			case Input::ACTION_MOUSE_LB_DBCLICK:
			case Input::ACTION_MOUSE_RB_DBCLICK:
				if( comp->CanHaveFocus() && comp->GetVisible() )
					RequestFocus( comp , updater );
			}

			if( comp->OnMouseInput( updater ) )
			{
				return INPUT_HANDLED;
			}
		}
		else
		{
			if( input.GetAction() == Input::ACTION_MOUSE_LB_CLICK && m_focusing.IsValid() )
			{
				ClearFocus( updater );
				result = INPUT_HANDLED;
			}
		}

		switch( input.GetAction() )
		{
		case Input::ACTION_MOUSE_LB_CLICK :
		case Input::ACTION_MOUSE_RB_CLICK:
			if( comp.IsNull() )
			{
				ClearFocus( updater );
				if( m_parent.IsValid() && !this->GetFocus() )
					m_parent->RequestFocus( this );
				result = INPUT_HANDLED;
			}
		}

		// Still not handled, hand this off to the dialog. Return false to indicate the
		// message should still be handled by the application (usually to move the camera).

		if( input.GetAction() == Input::ACTION_MOUSE_MOVE && !m_inputMode )
		{
			RefWeak<IGuiComp> new_hover = GetAtPoint( pt , true , true );

			if( new_hover.IsNull() )
			{
				// No component hit.
				if( m_hovering.IsValid() )
				{
					m_hovering->OnMouseLeave( updater , NULL );
					m_hovering = NULL;

					result = INPUT_HANDLED;
				}
			}
			else
			{
				// Hit!
				if( m_hovering != new_hover )
				{
					if( m_hovering.IsValid() )
					{
						m_hovering->OnMouseLeave( updater , new_hover );
						m_hovering = NULL;
					}

					new_hover->OnMouseEnter( updater , m_hovering );
					m_hovering = new_hover;

					result = INPUT_HANDLED;
				}
			}
			// FIXME: should be uncommented!
			//result = INPUT_HANDLED;
		}
		return result;
	}
	// Tick
	void XForm::Tick( Updater& updater , float dTime )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		if( m_parent.IsValid() )
		{
			XS_ASSERT( IsTickable() );
		}
		else
		{
			if( m_tickable.empty() )
				return;
		}

		std::set< RefWeak<IGuiComp> >::iterator it = m_tickable.begin();
		while( it != m_tickable.end() )
		{
			std::set< RefWeak<IGuiComp> >::iterator next = it;
			++next;
			RefWeak<IGuiComp> comp = *it;
			if( comp->GetVisible() )
				comp->Tick( updater , dTime );
			it = next;
		}
	}
	// Draw
	void XForm::Draw( Composition& composition )
	{
		COUNTER_GUARD( _S("xform_Draw") );

		Rect<int> rect( GetBound() );
		if( composition.BeginPaint( rect )  )
		{
			// Combine clipping.
			composition.graphics().SetClip( toRect( rect ) , Gdiplus::CombineModeIntersect );

			// Fill background.
			OnDrawBackground( composition );

			// Save current clip.
			ClippingGuard __clipguard( composition );

			// Set transform to current location.
 			float posX = float( m_offset.x );
 			float posY = float( m_offset.y );
			TransformGuard __xformguard( composition , posX , posY );

			Gdiplus::Rect bound;
			composition.graphics().GetClipBounds( &bound );
			const Rect<int> rc = make_rect( bound.X , bound.Y , bound.Width , bound.Height );

			// Update all children in revert order.
			for( int i = (int)m_children.size() - 1 ; i >= 0 ; --i )
			{
				RefWeak<IGuiComp> comp = m_children[i];
				if( comp->GetVisible() )
				{
					const Pair<int>& pos = comp->GetPosition();
					Rect<int> childbound = comp->GetPlacement();
					if( rc.is_overlap( childbound ) )
					{
						__xformguard.RestoreTransform();
						__clipguard.RestoreClip();

						composition.graphics().TranslateTransform( posX + float(pos.x) , posY + float(pos.y) , Gdiplus::MatrixOrderAppend );
						composition.graphics().SetClip( toRect( Rect<int>( 0 , 0 , comp->GetSize() + 1 ) ) , Gdiplus::CombineModeIntersect );
						comp->Draw( composition );
					}
				}
			}
			composition.EndPaint();
		}
	}
	// OnDrawBackground
	void XForm::OnDrawBackground( Composition& composition )
	{
		composition.graphics().FillRectangle( &Gdiplus::SolidBrush( Gdiplus::Color(133,133,133,233) ) , toRect( GetBound() ) );
	}
	// AddChild
	bool XForm::AddChild( RefWeak<IGuiComp> comp )
	{
		if( !comp->SetParent( this ) )
			return false;

		m_children.push_front( comp );
		ReorderChildren();
		UpdateControl();
		return true;
	}
	// DelChild
	bool XForm::DelChild( RefWeak<IGuiComp> comp )
	{
		if( comp->GetParent() != this )
			return false;
		for( size_t i = 0 , total = m_children.size() ; i < total ; ++i )
		{
			if( m_children[i] == comp )
			{
				DelTickable( comp );
				m_children.erase( m_children.begin() + i );
				comp->SetParent( NULL );
				return true;
			}
		}
		return false;
	}
	// AddTickable
	void XForm::AddTickable( RefWeak<IGuiComp> comp )
	{
		XS_ASSERT( comp->GetParent() == this );
		m_tickable.insert( comp );
		if( !m_tickable.empty() && m_parent.IsValid() )
		{
			m_parent->AddTickable( this );
		}
	}
	// DelTickable
	void XForm::DelTickable( RefWeak<IGuiComp> comp )
	{
		XS_ASSERT( comp->GetParent() == this );
		m_tickable.erase( comp );
		if( !IsTickable() && m_parent.IsValid() )
		{
			m_parent->DelTickable( this );
		}
	}
	// IsTickable
	bool XForm::IsTickable( void )
	{
		return !m_tickable.empty();
	}
	// CycleFocus
	void XForm::CycleFocus( bool forward , Updater& updater )
	{
		if( m_children.empty() )
			return;

		if( m_focusing.IsNull() )
		{
			m_focusing = m_children[0];
			m_focusing->OnFocusIn( updater , NULL );
			return;
		}

		for( size_t i = 0 , total = m_children.size() ; i < total ; ++i )
		{
			RefWeak<IGuiComp> comp = m_children[i];
			RefWeak<IGuiComp> ct = NULL;

			if( comp == m_focusing )
			{
				bool once = false;
				do {
					if( forward )
						++i;
					else
						--i;
					if( i < 0 )
					{
						if( once )
							return;
						once = true;
						i = total - 1;
					}
					else if( i >= total )
					{
						if( once )
							return;
						once = true;
						i = 0;
					}
					ct = m_children[i];
				} while(
					!ct->GetEnable() ||
					!ct->GetVisible() ||
					!ct->CanHaveFocus() );

				if( ct == m_focusing )
					return;

				RequestFocus( ct , updater );
				return;
			}
		}
		XS_ASSERT(false);
	}
	// OnMouseEnter
	void XForm::OnMouseEnter( Updater& updater , RefWeak<IGuiComp> last )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		if( updater.GetInput().IsMouseInput() )
		{
			Pair<int> pt = updater.GetPoint();

			RefWeak<IGuiComp> new_hover = GetAtPoint( pt , true , true );

			if( new_hover.IsNull() )
			{
				// No component hit.
				if( m_hovering.IsValid() )
				{
					m_hovering->OnMouseLeave( updater , NULL );
					m_hovering = NULL;
				}
			}
			else
			{
				// Hit!
				if( m_hovering.IsValid() )
				{
					m_hovering->OnMouseLeave( updater , new_hover );
					m_hovering = NULL;
				}

				new_hover->OnMouseEnter( updater , m_hovering );
				m_hovering = new_hover;
			}
		}
		__super::OnMouseEnter( updater , last );
	}
	// OnMouseLeave
	void XForm::OnMouseLeave( Updater& updater , RefWeak<IGuiComp> next )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		if( m_hovering.IsValid() )
		{
			m_hovering->OnMouseLeave( updater , next );
			m_hovering = NULL;
		}
		__super::OnMouseLeave( updater , next );
	}
	// OnFocusIn
	void XForm::OnFocusIn( Updater& updater , RefWeak<IGuiComp> last )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		__super::OnFocusIn( updater , last );
		// pass to child
		if( m_focusing.IsValid() )
		{
			m_focusing->OnFocusIn( updater , last );
		}
	}
	// OnFocusOut
	void XForm::OnFocusOut( Updater& updater , RefWeak<IGuiComp> next )
	{
		UpdateGuard __guard( this , updater , GetPosition() + m_offset );

		if( GetFocus() )
		{
			__super::OnFocusOut( updater , next );
			// clear focus
			ClearFocus( updater );
		}
	}
	// GetFocusing
	RefWeak<IGuiComp> XForm::GetFocusing( void )
	{
		return RefWeak<IGuiComp>(m_focusing);
	}
	// OnMouseHover
	void XForm::OnMouseHover( Updater& updater )
	{
		if( m_hovering.IsValid() )
		{
			m_hovering->OnMouseHover( updater );
		}
	}
	// GetAtPoint
	RefWeak<IGuiComp> XForm::GetAtPoint( const Pair<int>& point , bool enableMatch , bool visibleMatch )
	{
		for( size_t i = 0 , total = m_children.size() ; i < total ; ++i )
		{
			RefWeak<IGuiComp> comp = RefWeak<IGuiComp>( m_children[i] );
			if( ( enableMatch == comp->GetEnable() ) && ( visibleMatch == comp->GetVisible() ) && comp->HitTest( point ) )
				return comp;
		}
		return NULL;
	}
	// OnClose
	void XForm::OnClose( void )
	{
		for( size_t i = 0 , total = m_children.size() ; i < total ; ++i )
		{
			m_children[i]->OnClose();
		}
		m_children.clear();
	}
	// RequestUpdate
	void XForm::RequestUpdate( RefWeak<IGuiComp> comp , const Rect<int>& region )
	{
		if( region.is_empty() )
			return;
		Rect<int> r( region );
		r.offset( m_offset );
		if( comp != this )
		{
			XS_ASSERT( comp->GetParent() == this );

			if( !comp->GetVisible() )
				return;

			Rect<int> rect( GetBound() );
			if( rect.is_empty() )
				return;

			r.offset( comp->GetPosition() );
			if( r.is_empty() )
				return;
			r = r & rect;
			if( r.is_empty() )
				return;
		}

		RefWeak<IGuiForm> parent = GetParent();
		if( parent.IsValid() )
		{
			parent->RequestUpdate( this , r );
		}
	}

	namespace
	{
		bool sort_comp( const Ref<IGuiComp>& left , const Ref<IGuiComp>& right )
		{
			if( left->GetActiveId() == right->GetActiveId() )
				return left->GetId() > right->GetId();
			return left->GetActiveId() > right->GetActiveId();
		}
	}

	// ReorderChildren
	void XForm::ReorderChildren( void )
	{
		std::sort( m_children.begin() , m_children.end() , sort_comp );
	}
	// GetChild
	RefWeak<IGuiComp> XForm::GetChild( int index )
	{
		if( index < 0 || index >= (int)m_children.size() )
			return NULL;
		return m_children[index];
	}
	// SetCapture
	void XForm::SetCapture( RefWeak<IGuiComp> comp )
	{
		XS_ASSERT( comp->GetParent() == this );
		if( m_capturing.IsValid() && comp != m_capturing )
		{
			TRACE_ERROR( _S("XForm::SetCapture: Capture is not released!") );
			return;
		}
		m_capturing = comp;
		RefWeak<IGuiForm> parent = GetParent();
		if( parent.IsValid() )
		{
			parent->SetCapture( this );
		}
	}
	// GetCapture
	RefWeak<IGuiComp> XForm::GetCapture( void )
	{
		return m_capturing;
	}
	// ReleaseCapture
	void XForm::ReleaseCapture( void )
	{
		m_capturing = NULL;
		RefWeak<IGuiForm> parent = GetParent();
		if( parent.IsValid() && parent->GetCapture() == this )
		{
			parent->ReleaseCapture();
		}
	}

	// SetOffset
	void XForm::SetOffset( const Pair<int>& offset )
	{
		m_offset = offset;
		UpdateControl();
	}

	// GetOffset
	const Pair<int>& XForm::GetOffset( void ) const
	{
		return m_offset;
	}

} // namespace xeres
